import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;

/**
 * @author Aleksey Lagoshin
 * @version 1.0 27/4/2007 16:24:52
 */
public class BinaryServer extends JFrame {

  public static final String POLICY_REQUEST = "<policy-file-request/>";
  public static final String POLICY_RESPONSE = "<cross-domain-policy>\n" +
                                               "  <allow-access-from domain=\"*\" to-ports=\"*\" />\n" +
                                               "</cross-domain-policy>\n";

  private static int PORT;

  private Socket socket;
  private Vector<SocketThread> activeSockets = new Vector<SocketThread>();

  private JTextArea taMessages = new JTextArea();
  private JTextField tfInput = new JTextField();
  private JLabel lblAvailBytes = new JLabel("0");

  private boolean reading;

  public BinaryServer(String port) {
    PORT = Integer.valueOf(port);

    setSize(600, 600);

    setLayout(new BorderLayout());

    taMessages.setEditable(false);
    tfInput.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        tfInput.setText("");

        for (SocketThread sockThr : activeSockets)
          sockThr.send(msg);
        println(" >> " + msg);
      }
    });

    JButton btnReadBoolean = new JButton("Read Boolean");
    JButton btnReadByte = new JButton("Read Byte");
    JButton btnReadBytes = new JButton("Read Bytes");
    JButton btnReadDouble = new JButton("Read Double");
    JButton btnReadFloat = new JButton("Read Float");
    JButton btnReadInt = new JButton("Read Int");
    JButton btnReadShort = new JButton("Read Short");
    JButton btnReadUTF = new JButton("Read UTF");
    JButton btnReadString = new JButton("Read String");

    JButton btnSendBoolean = new JButton("Write Boolean");
    JButton btnSendByte = new JButton("Write Byte");
    JButton btnSendBytes = new JButton("Write Bytes");
    JButton btnSendDouble = new JButton("Write Double");
    JButton btnSendFloat = new JButton("Write Float");
    JButton btnSendInt = new JButton("Write Int");
    JButton btnSendShort = new JButton("Write Short");
    JButton btnSendUTF = new JButton("Write UTF");
    JButton btnSendString = new JButton("Write String");

    btnReadBoolean.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (activeSockets.size() > 0) try {
          println(" << " + activeSockets.get(0).getIn().readBoolean());
        }
        catch (IOException e1) {
          println(" ** " + e1.getMessage());
        }
      }
    });

    btnReadByte.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (activeSockets.size() > 0) try {
          println(" << " + activeSockets.get(0).getIn().readByte());
        }
        catch (IOException e1) {
          println(" ** " + e1.getMessage());
        }
      }
    });

    btnReadBytes.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (activeSockets.size() > 0) try {
          int count = Integer.parseInt(JOptionPane.showInputDialog("Enter the numbers of bytes to be read", activeSockets.get(0).getIn().available()));
          byte[] bytes = new byte[count];

          activeSockets.get(0).getIn().read(bytes);

          String bytesStr = "";
          for (int i = 0; i < count; i++)
            bytesStr += bytes[i] + " ";
          println(" << " + bytesStr);
        }
        catch (IOException e1) {
          println(" ** " + e1.getMessage());
        }
      }
    });

    btnReadDouble.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (activeSockets.size() > 0) try {
          println(" << " + activeSockets.get(0).getIn().readDouble());
        }
        catch (IOException e1) {
          println(" ** " + e1.getMessage());
        }
      }
    });

    btnReadFloat.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (activeSockets.size() > 0) try {
          println(" << " + activeSockets.get(0).getIn().readFloat());
        }
        catch (IOException e1) {
          println(" ** " + e1.getMessage());
        }
      }
    });

    btnReadInt.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (activeSockets.size() > 0) try {
          println(" << " + activeSockets.get(0).getIn().readInt());
        }
        catch (IOException e1) {
          println(" ** " + e1.getMessage());
        }
      }
    });

    btnReadShort.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (activeSockets.size() > 0) try {
          println(" << " + activeSockets.get(0).getIn().readShort());
        }
        catch (IOException e1) {
          println(" ** " + e1.getMessage());
        }
      }
    });

    btnReadUTF.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (activeSockets.size() > 0) try {
          println(" << " + activeSockets.get(0).getIn().readUTF());
        }
        catch (IOException e1) {
          println(" ** " + e1.getMessage());
        }
      }
    });

    btnReadString.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (activeSockets.size() > 0) try {
          String charSet = JOptionPane.showInputDialog("Enter the name of char set: ", "utf-8");
          int count = Integer.parseInt(JOptionPane.showInputDialog("Enter the numbers of bytes to be read", activeSockets.get(0).getIn().available()));
          byte[] bytes = new byte[count];
          activeSockets.get(0).getIn().read(bytes);
          println(" << " + new String(bytes, charSet));
        }
        catch (IOException e1) {
          println(" ** " + e1.getMessage());
        }
      }
    });

    btnSendBoolean.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        tfInput.setText("");

        for (SocketThread sockThr : activeSockets)
          try {
            sockThr.getOut().writeBoolean(Boolean.parseBoolean(msg));
          }
          catch (IOException e1) {
            e1.printStackTrace();
          }
        println(" >> " + msg);
      }
    });

    btnSendByte.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        tfInput.setText("");

        for (SocketThread sockThr : activeSockets)
          try {
            sockThr.getOut().writeByte(Integer.parseInt(msg));
          }
          catch (IOException e1) {
            e1.printStackTrace();
          }
        println(" >> " + msg);
      }
    });

    btnSendBytes.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        String[] bytesStr = msg.split(" ");
        tfInput.setText("");

        byte[] bytes = new byte[bytesStr.length];
        for (int i = 0; i < bytesStr.length; i++)
          bytes[i] = Byte.parseByte(bytesStr[i]);

        for (SocketThread sockThr : activeSockets)
          try {
            sockThr.getOut().write(bytes);
          }
          catch (IOException e1) {
            e1.printStackTrace();
          }
        println(" >> " + msg);
      }
    });

    btnSendDouble.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        tfInput.setText("");

        for (SocketThread sockThr : activeSockets)
          try {
            sockThr.getOut().writeDouble(Double.parseDouble(msg));
          }
          catch (IOException e1) {
            e1.printStackTrace();
          }
        println(" >> " + msg);
      }
    });

    btnSendFloat.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        tfInput.setText("");

        for (SocketThread sockThr : activeSockets)
          try {
            sockThr.getOut().writeFloat(Float.parseFloat(msg));
          }
          catch (IOException e1) {
            e1.printStackTrace();
          }
        println(" >> " + msg);
      }
    });

    btnSendInt.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        tfInput.setText("");

        for (SocketThread sockThr : activeSockets)
          try {
            sockThr.getOut().writeInt(Integer.parseInt(msg));
          }
          catch (IOException e1) {
            e1.printStackTrace();
          }
        println(" >> " + msg);
      }
    });

    btnSendShort.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        tfInput.setText("");

        for (SocketThread sockThr : activeSockets)
          try {
            sockThr.getOut().writeShort(Short.parseShort(msg));
          }
          catch (IOException e1) {
            e1.printStackTrace();
          }
        println(" >> " + msg);
      }
    });

    btnSendUTF.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        tfInput.setText("");

        for (SocketThread sockThr : activeSockets)
          try {
            sockThr.getOut().writeUTF(msg);
          }
          catch (IOException e1) {
            e1.printStackTrace();
          }
        println(" >> " + msg);
      }
    });

    btnSendString.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String msg = tfInput.getText();
        tfInput.setText("");

        String charSet = JOptionPane.showInputDialog("Enter the name of char set: ", "utf-8");

        for (SocketThread sockThr : activeSockets)
          try {
            sockThr.getOut().write(msg.getBytes(charSet));
          }
          catch (IOException e1) {
            e1.printStackTrace();
          }
        println(" >> " + msg);
      }
    });

    JScrollPane scrollPane;
    scrollPane = new JScrollPane(taMessages);

    JPanel buttonsPanel = new JPanel(new GridLayout(4, 3));
    buttonsPanel.add(btnSendBoolean);
    buttonsPanel.add(btnSendByte);
    buttonsPanel.add(btnSendBytes);
    buttonsPanel.add(btnSendDouble);
    buttonsPanel.add(btnSendFloat);
    buttonsPanel.add(btnSendInt);
    buttonsPanel.add(btnSendShort);
    buttonsPanel.add(btnSendUTF);
    buttonsPanel.add(btnSendString);
    buttonsPanel.add(btnReadBoolean);
    buttonsPanel.add(btnReadByte);
    buttonsPanel.add(btnReadBytes);
    buttonsPanel.add(btnReadDouble);
    buttonsPanel.add(btnReadFloat);
    buttonsPanel.add(btnReadInt);
    buttonsPanel.add(btnReadShort);
    buttonsPanel.add(btnReadUTF);
    buttonsPanel.add(btnReadString);

    JPanel bottomPanel = new JPanel(new BorderLayout());
    bottomPanel.add(tfInput, BorderLayout.CENTER);
    bottomPanel.add(buttonsPanel, BorderLayout.SOUTH);

    JPanel panel = new JPanel(new FlowLayout());
    panel.add(new JLabel("Available bytes: "));
    panel.add(lblAvailBytes);

    add(panel, BorderLayout.NORTH);
    add(scrollPane, BorderLayout.CENTER);
    add(bottomPanel, BorderLayout.SOUTH);
    setVisible(true);
  }

  public void initialize() {
    new Thread(new Runnable() {
      public void run() {
        try {
          ServerSocket srvSocket = new ServerSocket(PORT);
          taMessages.setText(" ** Listening port " + PORT + "...");
          while (true) {
            socket = srvSocket.accept();
            println(" ** Accepted client from IP - " + socket.getInetAddress());

            SocketThread sockThr = new SocketThread();
            sockThr.setSocket(socket);
            sockThr.start();

            activeSockets.add(sockThr);
          }
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }
    }).start();
  }

  private void println(String message) {
    taMessages.append(message + '\n');
    taMessages.scrollRectToVisible(new Rectangle(0, taMessages.getHeight() + 20, 1, 1));
  }

  public static void main(String[] args) {
    BinaryServer txtSrv = new BinaryServer(args.length > 0 ? args[0] : "777");
    txtSrv.setDefaultCloseOperation(EXIT_ON_CLOSE);
    txtSrv.initialize();
  }

  private class SocketThread extends Thread {

    private Socket socket;

    private InputStreamReader inReader;
    private DataInputStream in;
    private DataOutputStream out;

    public void run() {
      try {
//        in = new DataInputStream(socket.getInputStream());
        inReader = new InputStreamReader(socket.getInputStream(), "UTF-8");
        out = new DataOutputStream(socket.getOutputStream());
        read();
      }
      catch (IOException e) {
        e.printStackTrace();
      }
    }

    private void read() {
      StringBuffer buffer = new StringBuffer();

//      int availableBytes = 0;

      reading = true;
      boolean first = true;
      boolean isData = false;
      int b;
      try {
        while (reading) {
          if (!isData) {
            b = inReader.read();
            if (b > 0) {
              if (first) {
                first = false;
                if (b == 1) {
                  println(" ** Waiting for data...");
                  isData = true;
                }
              }
              buffer.append(new Character((char) b));
            }
            else if (b == -1)
              break;
            else {
              println(" << " + buffer.toString());
              if (buffer.toString().equals(POLICY_REQUEST)) {
                println(POLICY_RESPONSE);
                send(POLICY_RESPONSE);
              }
              buffer.setLength(0);
            }
          }
          else {
            final CheckingStream checkingStream = new CheckingStream(socket.getInputStream());
            checkingStream.addStreamListener(new StreamListener() {
              public void onData(InputStream in, int loadedBytes) {
                println(" << Received " + loadedBytes + " byte(s). Total bytes: " + checkingStream.available());
              }

              public void onClose(InputStream in) {
                reading = false;
              }
            });
            in = new DataInputStream(checkingStream);
            checkingStream.startReading();
            while (reading) {
              sleep(1000);
            }
            break;

//            int currBytes = in.available();
//            if (availableBytes < currBytes)
//              println(" << Received " + (currBytes - availableBytes) + " byte(s). Total bytes: " + currBytes);
//            availableBytes = currBytes;
//            lblAvailBytes.setText(String.valueOf(availableBytes));
//            sleep(500);
          }
        }
      }
      catch (IOException e) {
        e.printStackTrace();
      }
      catch (InterruptedException e) {
        e.printStackTrace();
      }
      finally {
        activeSockets.remove(this);
        println(" ** Client has been disconnected");
      }
    }

    public void send(String message) {
      try {
        out.write(message.getBytes("UTF-8"));
        out.write(0);
      }
      catch (IOException e) {
        e.printStackTrace();
      }
    }

    public void setSocket(Socket socket) {
      this.socket = socket;
    }

    public DataInputStream getIn() {
      return in;
    }

    public DataOutputStream getOut() {
      return out;
    }

  }

}
